﻿using System;
using System.Linq;

namespace Central.Linq {

	/// <summary>
	/// Расширение класса IQueryable
	/// </summary>
	public static class IQueryableExtensions {

		/// <summary>
		/// Метод формирования запроса к данным
		/// </summary>
		/// <typeparam name="TSource"></typeparam>
		/// <typeparam name="TResult"></typeparam>
		/// <param name="source">исходный запрос</param>
		/// <param name="par">объект параметров</param>
		/// <returns>результирующий запрос с учетом параметров</returns>
		public static IQueryable<TResult> PrepareQuery<TSource, TResult>(this IQueryable<TSource> source, QueryParams par) {
			if (source == null) throw new NullReferenceException("source");

			if (par == null)
				return (IQueryable<TResult>)source;

			//try
			//{
			IQueryable<TResult> result = null;

			#region Базовая выборка по коллекции par.Fields

			if (par.Fields == null || par.Fields.Count == 0) {
				if (!typeof(TSource).Equals(typeof(TResult)))
					throw new ArgumentNullException("par", "Fields Not Found in QueryParam");
				else
					result = (IQueryable<TResult>)source;
			}
			else
				result = source.SelectByFields<TResult>(par.Fields);

			#endregion Базовая выборка по коллекции par.Fields

			#region фильтрация

			if (par.Filters != null && par.Filters.Count > 0) {
				FilterPredicate filterPredicate = par.Filters.Predicate;
				if (filterPredicate != null)
					// фильтрация
					result = result.Where(filterPredicate.Where, filterPredicate.Values.ToArray());
			}

			#endregion фильтрация

			#region группировка

			if (par.Groups != null && par.Groups.Count > 0) {
				GroupPredicate groupPredicate = par.Groups.Predicate;
				if (groupPredicate != null)
					// группировка
					result = result.GroupBy(groupPredicate.Group, "it").Select<TResult>(groupPredicate.Select);
			}

			#endregion группировка

			#region сортировка

			// изменение сортировки на основе заданной группировки и коллекции полей
			par.Sorters = par.Sorters.ChangeSortersByGroups(par.Groups, par.Fields);

			if (par.Sorters != null && par.Sorters.Count > 0) {
				// сортировка
				result = result.OrderBy(par.Sorters.Predicate);
			}

			#endregion сортировка

			//}
			//catch
			//{
			//}

			return result;
		}

		/// <summary>
		/// Метод формирования запроса к данным
		/// </summary>
		/// <typeparam name="TSource"></typeparam>
		/// <param name="source">исходный запрос</param>
		/// <param name="par">объект параметров</param>
		/// <returns>результирующий запрос с учетом параметров</returns>
		public static IQueryable PrepareQuery<TSource>(this IQueryable<TSource> source, QueryParams par) {
			if (source == null) throw new NullReferenceException("source");

			IQueryable result = source;

			if (par == null)
				return result;

			//try
			//{
			#region Базовая выборка по коллекции par.Fields

			result = result.SelectByFields(par.Fields);

			#endregion Базовая выборка по коллекции par.Fields

			#region фильтрация

			if (par.Filters != null && par.Filters.Count > 0) {
				// фильтрация
				result = result.Where(par.Filters.Predicate.Where, par.Filters.Predicate.Values.ToArray());
			}

			#endregion фильтрация

			#region группировка

			if (par.Groups != null && par.Groups.Count > 0) {
				// группировка
				result = result.GroupBy(par.Groups.Predicate.Group, "it").Select(par.Groups.Predicate.Select);
			}

			#endregion группировка

			#region сортировка

			// изменение сортировки на основе заданной группировки и коллекции полей
			par.Sorters = par.Sorters.ChangeSortersByGroups(par.Groups, par.Fields);

			if (par.Sorters != null && par.Sorters.Count > 0) {
				// сортировка
				result = result.OrderBy(par.Sorters.Predicate);
			}

			#endregion сортировка

			//}
			//catch
			//{
			//}

			return result;
		}

		/// <summary>
		/// Выборка по заданной коллекции полей
		/// </summary>
		/// <param name="source">исходный запрос</param>
		/// <param name="fields">коллекция полей</param>
		/// <returns>результирующий запрос</returns>
		public static IQueryable SelectByFields(this IQueryable source, FieldManager fields) {
			if (source == null) throw new NullReferenceException("source");

			if (fields == null || fields.Count == 0)
				return source;

			return source.Select(fields.Predicate.Select, fields.Predicate.Values.ToArray());
		}

		/// <summary>
		/// Выборка по заданной коллекции полей
		/// </summary>
		/// <param name="source">исходный запрос</param>
		/// <param name="fields">коллекция полей</param>
		/// <returns>результирующий запрос</returns>
		public static IQueryable<T> SelectByFields<T>(this IQueryable source, FieldManager fields) {
			if (source == null) throw new NullReferenceException("source");

			if (fields == null) {
				throw new ArgumentNullException("fields");
			}

			return source.Select<T>(fields.Predicate.Select, fields.Predicate.Values.ToArray());
		}
	}
}